package com.blank.shop.ssr.controller;

import cn.hutool.core.util.RandomUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.Duration;
import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * <br/>Date 2021/9/2
 * <br/>Time 18:39:30
 *
 * @author _blank
 */
@Slf4j
@RestController
public class GoodsController {

    @Value("${server.port}")
    private Integer serverPort;

    @Resource
    private RedisTemplate<String, Integer> redisTemplate;
    @Resource
    private RedissonClient redisson;

    /**
     * string操作
     */
    private ValueOperations<String, Integer> valueOps;


    @PostConstruct
    public void initBean() {
        this.valueOps = this.redisTemplate.opsForValue();
    }

    @GetMapping(path = {"buy-commodity"})
    public ResponseEntity<String> buyCommodity() {
        return this.redisson(() -> {
            final String goodsId = "buy-commodity:001";
            final Integer goodsNumber = this.valueOps.get(goodsId);
            if (Objects.nonNull(goodsNumber) && goodsNumber > 0) {
                final int realNumber = goodsNumber - 1;
                this.valueOps.set(goodsId, realNumber);
                return ResponseEntity.ok("服务提供端口 " + this.serverPort + "，购买商品成功，商品库存还剩下" + realNumber + "件！");
            }
            return ResponseEntity.ok("服务提供端口 " + this.serverPort + "，商品已经售完/活动结束/调用超时！~~~");
        });
    }

    private ResponseEntity<String> redisson(Supplier<ResponseEntity<String>> supplier) {
        final String lockFlagGoods = "lock_flag_goods";

        final RLock redissonClientLock = this.redisson.getLock(lockFlagGoods);
        redissonClientLock.lock();
        try {
            return supplier.get();
        } finally {
            // 检查锁是否被任何线程锁定 (是否还是锁定状态) && 检查当前线程是否持有此锁 (是否是当前执行线程的锁)
            if (redissonClientLock.isLocked() && redissonClientLock.isHeldByCurrentThread()) {
                redissonClientLock.unlock();
            }
        }
    }

    private ResponseEntity<String> setnx(Supplier<ResponseEntity<String>> supplier) {
        final String lockFlagGoods = "lock_flag_goods";
        // SETNX 操作
        final int flagValue = RandomUtil.getRandom().nextInt(1000000, 9999999);
        try {
            final Boolean setIfAbsent = this.valueOps.setIfAbsent(lockFlagGoods, flagValue, 30L, TimeUnit.SECONDS);
            if (BooleanUtils.isTrue(setIfAbsent)) {
                // do business
                return supplier.get();
            }
            return ResponseEntity.badRequest().body("处理出错！");
        } finally {
//            // 非原子操作
//            if (Objects.equals(flagValue, this.valueOps.get(lockFlagGoods))) {
//                // 只可以解自己加的锁
//                this.redisTemplate.delete(lockFlagGoods);
//            }

//            // 事务操作
//            try {
//                this.redisTemplate.watch(lockFlagGoods);
//                boolean doWhile = true;
//                // 重试次数
//                int retry = 9;
//                do {
//                    if (Objects.equals(flagValue, this.valueOps.get(lockFlagGoods))) {
//                        this.redisTemplate.setEnableTransactionSupport(true);
//                        this.redisTemplate.multi();
//                        this.redisTemplate.delete(lockFlagGoods);
//                        final List<Object> exec = this.redisTemplate.exec();
//                        if (CollectionUtils.isEmpty(exec) || retry-- > 0) {
//                            continue;
//                        }
//                    }
//                    doWhile = false;
//                } while (doWhile);
//            } finally {
//                this.redisTemplate.unwatch();
//            }

            // lua脚本 https://redis.io/commands/set#patterns
            final String luaScript = ""
                    + "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n"
                    + "then\n"
                    + "    return redis.call(\"del\",KEYS[1])\n"
                    + "else\n"
                    + "    return 0\n"
                    + "end";
            final Long respFlag = this.redisTemplate.execute(
                    RedisScript.of(luaScript, Long.class)
                    , Collections.singletonList(lockFlagGoods)
                    , flagValue);
            log.info(Objects.equals(1L, respFlag) ? "解锁成功！" : "解锁失败！");
        }
    }

    // /////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    @Resource
    private RedissonClient redissonClientMasterAa;
    @Resource
    private RedissonClient redissonClientMasterBb;
    @Resource
    private RedissonClient redissonClientMasterCc;

    /**
     * redisson 多主机案例，保证高可用
     */
    public void multiRedissonClient() {
        final String name = "redisson-lock-product";
        final RLock lockAa = this.redissonClientMasterAa.getLock(name);
        final RLock lockBb = this.redissonClientMasterBb.getLock(name);
        final RLock lockCc = this.redissonClientMasterCc.getLock(name);

        final RedissonRedLock redLock = new RedissonRedLock(lockAa, lockBb, lockCc);
        try {
            // waitTime 锁的等待时间处理,正常情况下 等5s
            // leaseTime就是redis key的过期时间,正常情况下等5分钟。
            if (redLock.tryLock(Duration.ofSeconds(5L).getSeconds(), Duration.ofMinutes(5L).getSeconds(), TimeUnit.SECONDS)) {
                log.info("线程是拿到锁！");

                // 模拟业务执行20秒
                TimeUnit.SECONDS.sleep(20L);

            } else {
                log.info("线程否拿到锁！");
            }

        } catch (InterruptedException e) {
            log.error(e.getMessage(), e);
            Thread.currentThread().interrupt();
        } finally {
            // 无论如何, 最后都要解锁
            if (redLock.isLocked() && redLock.isHeldByCurrentThread()) {
                redLock.unlock();
            }
        }
    }


}
